In [ ]:
epochs = 10
# We don't use the whole dataset for efficiency purpose, but feel free to increase these numbers
n_train_items = 640
n_test_items = 640
在构建机器学习即服务解决方案(MLaaS)时,公司可能需要请求其他合作伙伴访问数据以训练其模型。在卫生或金融领域,模型和数据都非常关键:模型参数是业务资产,而数据是严格监管的个人数据。
在这种情况下,一种可能的解决方案是对模型和数据都进行加密,并在加密后的值上训练机器学习模型。例如,这保证了公司不会访问患者的病历,并且医疗机构将无法观察他们贡献的模型。存在几种允许对加密数据进行计算的加密方案,其中包括安全多方计算(SMPC),同态加密(FHE / SHE)和功能加密(FE)。我们将在这里集中讨论多方计算(已在教程5中进行了介绍),它由私有加性共享组成,并依赖于加密协议SecureNN和SPDZ。
本教程的确切设置如下:考虑您是服务器,并且您想对$n$个工作机持有的某些数据进行模型训练。服务器机密共享他的模型,并将每个共享发送给工作机。工作机秘密共享他们的数据并在他们之间交换。在我们将要研究的配置中,有2个工作机:alice和bob。交换共享后,他们每个人现在拥有自己的共享,另一工作机的数据共享和模型共享。现在,计算可以开始使用适当的加密协议来私下训练模型。训练模型后,所有共享都可以发送回服务器以对其进行解密。下图对此进行了说明:
为了举例说明这个过程,让我们假设alice和bob都拥有MNIST数据集的一部分,然后训练一个模型来执行数字分类!
作者:
中文版译者:
In [ ]:
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets, transforms
import time
此类描述了训练的所有超参数。 请注意,它们在这里都是公开的。
In [ ]:
class Arguments():
def __init__(self):
self.batch_size = 64
self.test_batch_size = 64
self.epochs = epochs
self.lr = 0.02
self.seed = 1
self.log_interval = 1 # Log info at each batch
self.precision_fractional = 3
args = Arguments()
_ = torch.manual_seed(args.seed)
这是PySyft的进口商品。 我们连接到两个名为alice
和bob
的远程工作机,并请求另一个名为crypto_provider
的工作机,它提供了我们可能需要的所有加密原语。
In [ ]:
import syft as sy # import the Pysyft library
hook = sy.TorchHook(torch) # hook PyTorch to add extra functionalities like Federated and Encrypted Learning
# simulation functions
def connect_to_workers(n_workers):
return [
sy.VirtualWorker(hook, id=f"worker{i+1}")
for i in range(n_workers)
]
def connect_to_crypto_provider():
return sy.VirtualWorker(hook, id="crypto_provider")
workers = connect_to_workers(n_workers=2)
crypto_provider = connect_to_crypto_provider()
In [ ]:
def get_private_data_loaders(precision_fractional, workers, crypto_provider):
def one_hot_of(index_tensor):
"""
Transform to one hot tensor
Example:
[0, 3, 9]
=>
[[1., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 1., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0., 1.]]
"""
onehot_tensor = torch.zeros(*index_tensor.shape, 10) # 10 classes for MNIST
onehot_tensor = onehot_tensor.scatter(1, index_tensor.view(-1, 1), 1)
return onehot_tensor
def secret_share(tensor):
"""
Transform to fixed precision and secret share a tensor
"""
return (
tensor
.fix_precision(precision_fractional=precision_fractional)
.share(*workers, crypto_provider=crypto_provider, requires_grad=True)
)
transformation = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])
train_loader = torch.utils.data.DataLoader(
datasets.MNIST('../data', train=True, download=True, transform=transformation),
batch_size=args.batch_size
)
private_train_loader = [
(secret_share(data), secret_share(one_hot_of(target)))
for i, (data, target) in enumerate(train_loader)
if i < n_train_items / args.batch_size
]
test_loader = torch.utils.data.DataLoader(
datasets.MNIST('../data', train=False, download=True, transform=transformation),
batch_size=args.test_batch_size
)
private_test_loader = [
(secret_share(data), secret_share(target.float()))
for i, (data, target) in enumerate(test_loader)
if i < n_test_items / args.test_batch_size
]
return private_train_loader, private_test_loader
private_train_loader, private_test_loader = get_private_data_loaders(
precision_fractional=args.precision_fractional,
workers=workers,
crypto_provider=crypto_provider
)
这是我们将使用的模型,它是一个相当简单的模型,但是已证明在MNIST上表现良好。
In [ ]:
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.fc1 = nn.Linear(28 * 28, 128)
self.fc2 = nn.Linear(128, 64)
self.fc3 = nn.Linear(64, 10)
def forward(self, x):
x = x.view(-1, 28 * 28)
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
In [ ]:
def train(args, model, private_train_loader, optimizer, epoch):
model.train()
for batch_idx, (data, target) in enumerate(private_train_loader): # <-- now it is a private dataset
start_time = time.time()
optimizer.zero_grad()
output = model(data)
# loss = F.nll_loss(output, target) <-- not possible here
batch_size = output.shape[0]
loss = ((output - target)**2).sum().refresh()/batch_size
loss.backward()
optimizer.step()
if batch_idx % args.log_interval == 0:
loss = loss.get().float_precision()
print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}\tTime: {:.3f}s'.format(
epoch, batch_idx * args.batch_size, len(private_train_loader) * args.batch_size,
100. * batch_idx / len(private_train_loader), loss.item(), time.time() - start_time))
测试功能不变!
In [ ]:
def test(args, model, private_test_loader):
model.eval()
test_loss = 0
correct = 0
with torch.no_grad():
for data, target in private_test_loader:
start_time = time.time()
output = model(data)
pred = output.argmax(dim=1)
correct += pred.eq(target.view_as(pred)).sum()
correct = correct.get().float_precision()
print('\nTest set: Accuracy: {}/{} ({:.0f}%)\n'.format(
correct.item(), len(private_test_loader)* args.test_batch_size,
100. * correct.item() / (len(private_test_loader) * args.test_batch_size)))
In [ ]:
model = Net()
model = model.fix_precision().share(*workers, crypto_provider=crypto_provider, requires_grad=True)
optimizer = optim.SGD(model.parameters(), lr=args.lr)
optimizer = optimizer.fix_precision()
for epoch in range(1, args.epochs + 1):
train(args, model, private_train_loader, optimizer, epoch)
test(args, model, private_test_loader)
在这!使用MNIST数据集的一小部分,使用100%加密的训练,您只能获得75%的准确性!
第一件事显然是运行时间!您肯定已经注意到,它比明文训练要慢得多。特别是,在1批64项上进行一次迭代需要3.2s,而在纯PyTorch中只有13 ms。尽管这似乎是一个阻塞程序,但请回想一下,这里的所有事情都是远程发生的,并且是在加密的世界中发生的:没有单个数据项被公开。更具体地说,处理一项的时间是50ms,这还不错。真正的问题是分析何时需要加密训练以及何时仅加密预测就足够了。例如,在生产就绪的情况下,完全可以接受50毫秒执行预测!
一个主要的瓶颈是昂贵的激活功能的使用:SMPC的relu激活非常昂贵,因为它使用私有比较和SecureNN协议。例如,如果我们用二次激活代替relu,就像在CryptoNets等加密计算的几篇论文中所做的那样,我们将从3.2s降到1.2s。
通常,关键思想是仅加密必需的内容,本教程向您展示它可以多么简单。
In [ ]:
model.fc3.bias
和一个数据项:
In [ ]:
first_batch, input_data = 0, 0
private_train_loader[first_batch][input_data]
正如您所看到的,AutogradTensor在那里! 它位于Torch包装器和FixedPrecisionTensor之间,这表明值现在位于有限域中。此AutogradTensor的目标是在对加密值进行操作时存储计算图。这很有用,因为在向后调用反向传播时,此AutogradTensor会覆盖所有与加密计算不兼容的向后函数,并指示如何计算这些梯度。 例如,对于使用Beaver三元组技巧完成的乘法,我们不想再对技巧进行区分,因为区分乘法应该非常容易:$\partial_b(a\cdot b)= \cdot \partial b$。 例如,这是我们描述如何计算这些梯度的方法:
class MulBackward(GradFunc):
def __init__(self, self_, other):
super().__init__(self, self_, other)
self.self_ = self_
self.other = other
def gradient(self, grad):
grad_self_ = grad * self.other
grad_other = grad * self.self_ if type(self.self_) == type(self.other) else None
return (grad_self_, grad_other)
如果您想知道更多我们如何实现梯度的,可以查看tensors / interpreters / gradients.py
。
就计算图而言,这意味着该图的副本保留在本地,并且协调正向传递的服务器还提供有关如何进行反向传递的指令。 在我们的环境中,这是一个完全正确的假设。
最后,让我们给出一些有关我们在此处实现的安全性的提示:我们正在考虑的对手诚实但好奇:这意味着对手无法通过运行此协议来了解有关数据的任何信息,但是恶意的对手仍可能偏离协议,例如尝试破坏共享以破坏计算。在这样的SMPC计算(包括私有比较)中针对恶意对手的安全性仍然是一个未解决的问题。
此外,即使安全多方计算确保不访问训练数据,此处仍然存在来自纯文本世界的许多威胁。例如,当您可以向模型提出请求时(在MLaaS的上下文中),您可以获得可能泄露有关训练数据集信息的预测。特别是,您没有针对成员资格攻击的任何保护措施,这是对机器学习服务的常见攻击,在这种攻击中,对手想确定数据集中是否使用了特定项目。除此之外,其他攻击,例如意外的记忆过程(学习有关数据项特定特征的模型),模型反演或提取仍然可能。
对上述许多威胁有效的一种通用解决方案是添加差异隐私。它可以与安全的多方计算完美地结合在一起,并且可以提供非常有趣的安全性保证。我们目前正在研究几种实现方式,并希望提出一个将两者结合起来的示例!
祝贺您完成本笔记本教程! 如果您喜欢此方法,并希望加入保护隐私、去中心化AI和AI供应链(数据)所有权的运动,则可以通过以下方式做到这一点!
帮助我们的社区的最简单方法是仅通过给GitHub存储库加注星标! 这有助于提高人们对我们正在构建的出色工具的认识。
我们编写了非常不错的教程,以更好地了解联合学习和隐私保护学习的外观,以及我们如何为实现这一目标添砖加瓦。
保持最新进展的最佳方法是加入我们的社区! 您可以通过填写以下表格来做到这一点http://slack.openmined.org
对我们的社区做出贡献的最好方法是成为代码贡献者! 您随时可以转到PySyft GitHub的Issue页面并过滤“projects”。这将向您显示所有概述,选择您可以加入的项目!如果您不想加入项目,但是想做一些编码,则还可以通过搜索标记为“good first issue”的GitHub问题来寻找更多的“一次性”微型项目。
如果您没有时间为我们的代码库做贡献,但仍想提供支持,那么您也可以成为Open Collective的支持者。所有捐款都将用于我们的网络托管和其他社区支出,例如黑客马拉松和聚会!
In [ ]:
In [ ]: